using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace Microsoft.Samples.Windows.Forms.Navigation
{
	/// <summary>
	/// A browser-like frame for navigating among pages created with Windows Forms instead of web pages.
	/// </summary>
	[DefaultEvent("Navigate")]
	public class Frame : System.Windows.Forms.UserControl
	{
		/// <summary>
		/// Occurs when the frame has navigated to a new page.
		/// </summary>
		[Category("Behavior")]
		[Description("Occurs when the frame has navigated to a new page.")]
		public event NavigateEventHandler Navigate;

		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;
		private System.Windows.Forms.HScrollBar horizontalScrollBar;
		private System.Windows.Forms.VScrollBar verticalScrollBar;
		private System.Windows.Forms.Panel pageSite;

		/// <summary>
		/// The page that will be used by the frame as a home page.
		/// </summary>
		private Page homePage;
	
		/// <summary>
		/// The current state of navigation: what page the user is looking at, and
		/// what patterns are currently running.
		/// </summary>
		private NavigationContext currentContext = null;

		/// <summary>
		/// The stack of pages the user has previously viewed.
		/// </summary>
		private NavigationStack backStack;

		/// <summary>
		/// The stack of pages the user has viewed, then backed up from.
		/// </summary>
		private NavigationStack forwardStack;

		/// <summary>
		/// A table of reference counts to pages hosted in this frame.
		/// </summary>
		private ReferenceCountTable pageReferenceCount;

		/// <summary>
		/// A cache of page types the user has viewed.
		/// </summary>
		private TypeInstanceCache pageTypeInstanceCache;

		private EventHandler pageScrollBarsChangedHandler;

		/// <summary>
		/// Create a new frame
		/// </summary>
		public Frame()
		{
			InitializeComponent();

			// We only need to track navigation history when we're actually running, not designing.
			if (!DesignMode)
			{
				backStack = new NavigationStack();
				forwardStack = new NavigationStack();
				pageReferenceCount = new ReferenceCountTable();
				pageTypeInstanceCache = new TypeInstanceCache();
				pageScrollBarsChangedHandler = new EventHandler(this.Page_ScrollBarsChanged);
			}
		}

		/// <summary>
		/// Add the specified page to the frame.
		/// </summary>
		/// <param name="template">The page to add</param>
		private void AddPageToFrame(Page page)
		{
			// Keep track of the fact that we're now viewing another appearance of this page.
			pageReferenceCount.AddReference(page);

			// Add the page to the frame.
			pageSite.Controls.Add(page);
			page.Location = new System.Drawing.Point(0, 0);

			// Tell the page what its frame is.
			page.Frame = this;
		}

		/// <summary>
		/// Adjust the values of one of the scroll bars used to scroll the 
		/// page horizontally or vertically.
		/// </summary>
		/// <param name="scrollBar">The scroll bar to adjust</param>
		/// <param name="VisibleLength">The amount of horizontal|vertical space currently visible</param>
		/// <param name="ActualLength">The actual horizontal|vertical size of the page</param>
		private void AdjustScrollBar(ScrollBar scrollBar, int visiblePageLength, int actualPageLength)
		{
			if (visiblePageLength >= actualPageLength)
			{
				// We have enough space to show the whole page.
				scrollBar.Value = 0;
				scrollBar.Enabled = false;
			}
			else if (visiblePageLength > 0)
			{
				// We need to allow the user to scroll.
				scrollBar.Maximum = actualPageLength;
				scrollBar.LargeChange = (int) (((double) visiblePageLength / actualPageLength) * scrollBar.Maximum);
				scrollBar.SmallChange = scrollBar.LargeChange / 8;
				scrollBar.Enabled = true;

				if (scrollBar.Maximum - scrollBar.LargeChange < scrollBar.Value)
				{
					/*
					 * User has increased frame size to point where we're now showing the bottom/right edge
					 * of page, and we need to start scrolling the page up/left in order to keep this page edge
					 * pegged to the edge of the frame.
					 */
					scrollBar.Value = scrollBar.Maximum - scrollBar.LargeChange;
				}
			}
		}

		/// <value>
		/// The current context: the current page, plus any page state information.
		/// </value>
		/// <remarks>
		/// Setting the current context causes the context's page to be shown,
		/// and hides any previously visible page. This will not update the back and
		/// forward stacks; that job is left to the caller.
		/// 
		/// This property is null until the first navigation occurs. Attempting to
		/// explicitly set this property to null throws a NullReferenceException.
		/// </remarks>
		private NavigationContext CurrentContext
		{
			get
			{
				return currentContext;
			}
			set
			{
				if (value == null)
				{
					throw(new NullReferenceException());
				}

				/*
				 * Save the current state of the page and frame in the context, where it will get saved
				 * in the navigation history.
				 */
				NavigationContext previousContext = currentContext;
				Page previousPage = null;
				if (previousContext != null)
				{
					// Remember the state of the scroll bars.
					previousContext.HorizontalScrollPosition = HorizontalScrollPosition;
					previousContext.VerticalScrollPosition = VerticalScrollPosition;

					previousPage = previousContext.Page;
					if (previousPage != null)
					{
						/*
						 * Give the previous page a chance to save its state
						 * by trigger the page's PageLeave event.
						 */
						previousContext.ViewState = new ViewState();
						PageEventArgs e = new PageEventArgs(previousContext.ViewState);
						previousPage.OnPageLeave(e);

						// Stop listening for changes to the page's ScrollBars property.
						previousPage.ScrollBarsChanged -= pageScrollBarsChangedHandler;
					}
				}

				// Setting the current context implicitly changes the current page.
				currentContext = value;
				Page currentPage = CurrentPage;

				// Resize the page, showing scroll bars as appropriate.
				SetupScrollBarsForPage(currentPage);

				if (currentPage != null)
				{
					// Make the page fit the frame, if the page so desires.
					ResizePage(currentPage);

					// Restore the state of the scroll bars.
					HorizontalScrollPosition = currentContext.HorizontalScrollPosition;
					VerticalScrollPosition = currentContext.VerticalScrollPosition;

					/*
					 * Give the page a chance to set up by triggering its
					 * PageEnter event.
					 */				
					PageEventArgs e = new PageEventArgs(currentContext.ViewState);
					currentPage.OnPageEnter(e);

					/*
					 * Adopt the current page's background color, in case the page
					 * doesn't fill up the frame.
					 */
					pageSite.BackColor = currentPage.BackColor;
          
					// Show the new page.
					currentPage.Show();
					pageSite.Show();

					// Listen for changes to the page's ScrollBars property.
					currentPage.ScrollBarsChanged += pageScrollBarsChangedHandler;
				}
				else
				{
					// The new context doesn't want to show any page.
					pageSite.Hide();
				}

				/*
				 * Hide the previous page. This is done after showing the current page
				 * to avoid momentarily showing an empty frame.
				 */
				if (previousPage != null && previousPage != currentPage)
				{
					previousPage.Hide();
				}

				// Notify event handlers that we've navigated to a new page.
				OnNavigate(new NavigateEventArgs(currentPage));
			}
		}

		/// <value>
		/// True if there currently is a page to back up to.
		/// </value>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool CanGoBack
		{
			get
			{
				return (backStack.Count > 0);
			}
		}

		/// <value>
		/// True if there currently is a page to go forward to.
		/// </value>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool CanGoForward
		{
			get
			{
				return (forwardStack.Count > 0);
			}
		}

		/// <value>
		/// True if it's presently okay to leave the current page, false if not.
		/// </value>
		/// <remarks>
		/// The frame's parent should check this property before closing to make sure
		/// the current page can be safely closed in its current state.
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool CanLeavePage
		{
			get
			{
				return (CurrentPage == null) ? true : CurrentPage.CanLeave();
			}
		}

		/// <value>
		/// The page currently shown in the frame.
		/// </value>
		/// <remarks>
		/// Setting this property is the same as navigating. If no navigation has taken place yet,
		/// this property is null.
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Page CurrentPage
		{
			get
			{
				return (currentContext != null) ? currentContext.Page : null;
			}
			set
			{
				// Navigate to the new page.
				Go(value);
			}
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}
		
		/// <value>
		/// The titles of the pages on the back stack.
		/// </value>
		/// <param name="maximumPageTitleCount">If greater than zero, the maximum
		/// number of page titles that will be returned</param>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string[] GetBackStackPageTitles(int maximumPageTitleCount)
		{
			return backStack.GetPageTitles(maximumPageTitleCount);
		}

		/// <value>
		/// The titles of the pages on the forward stack.
		/// </value>
		/// <param name="maximumPageTitleCount">If greater than zero, the maximum
		/// number of page titles that will be returned</param>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string[] GetForwardStackPageTitles(int maximumPageTitleCount)
		{
			return forwardStack.GetPageTitles(maximumPageTitleCount);
		}

		/// <summary>
		/// Navigate to the specified page.
		/// </summary>
		/// <param name="page">The page to navigate to</param>
		public virtual void Go(Page page)
		{
			if (!CanLeavePage)
			{
				// The current page doesn't want us to leave yet.
				return;
			}

			if (page != null && !page.CanEnter(null))
			{
				// The new page doesn't really want to be shown.
				return;
			}

			if (CurrentContext != null)
			{
				// Push the current context on the back stack.
				backStack.Push(CurrentContext);
			}
			
			// Clear the forward stack of pages and remove those pages from the frame.
			while (forwardStack.Count > 0)
			{
				NavigationContext popContext = (NavigationContext) forwardStack.Pop();
				Page popPage = popContext.Page;

				if (popPage != null)
				{
					RemovePageFromFrame(popPage);
				}
			}

			if (page != null)
			{
				// Host the page in this frame.
				AddPageToFrame(page);
			}

			// Display the page in a new context.
			CurrentContext = new NavigationContext(page);
		}

		/// <summary>
		/// Navigate to an instance of the given page type.
		/// </summary>
		/// <remarks>
		/// This overloaded Go method should be used if an application only wants to ever create a single
		/// instance of the given page type. This can be done, for example, if the given type of page will
		/// always look the same no matter how the user visits the page.
		/// 
		/// If this is the first time the user has navigated to this type of page, a new instance of the
		/// given page type will be created. If the user has already navigated to this type of page
		/// via a previous call to this Go() method, the previously created instance of that page type
		/// will be reused.
		/// </remarks>
		/// <param name="pageType">The type of page to navigate to</param>
		public void Go(Type pageType)
		{
			Page page = (Page) pageTypeInstanceCache.GetInstanceOfType(pageType);
			Go(page);
		}

		/// <summary>
		/// Navigate back one page.
		/// </summary>
		/// <remarks>
		/// This has no effect if the back stack is empty.
		/// </remarks>
		public void GoBack()
		{
			MoveThroughHistory(-1);
		}
		
		/// <summary>
		/// Go back n pages.
		/// </summary>
		/// <param name="n">The number of pages to go back</param>
		/// <remarks>
		/// If the back stack is empty, this routine has no effect.
		/// </remarks>
		public void GoBack(int n)
		{
			MoveThroughHistory(-n);
		}

		/// <summary>
		/// Navigate forward one page.
		/// </summary>
		/// <remarks>
		/// This has no effect if the forward stack is empty.
		/// </remarks>
		public void GoForward()
		{
			MoveThroughHistory(1);
		}
		
		/// <summary>
		/// Go forward n pages.
		/// </summary>
		/// <param name="n">The number of pages to move forward</param>
		/// <remarks>
		/// If the forward stack is empty, this routine has no effect.
		/// </remarks>
		public void GoForward(int n)
		{
			MoveThroughHistory(n);
		}
		
		/// <summary>
		/// Navigate to the home page.
		/// </summary>
		/// <remarks>
		/// This routine has no effect if no home page has been defined, or if the
		/// home page is already the current page.
		/// </remarks>
		public void GoHome()
		{
			if (HomePage != null && CurrentPage != HomePage)
			{
				Go(HomePage);
			}
		}

		/// <value>
		/// The home page used by the frame's GoHome method.
		/// </value>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Page HomePage
		{
			get
			{
				return homePage;
			}
			set
			{
				homePage = value;
			}
		}

		/// <summary>
		/// The horizontal scroll bar has been moved.
		/// </summary>
		private void horizontalScrollBar_ValueChanged(object sender, System.EventArgs e)
		{
			SyncPageToHorizontalScrollBar();
		}

		/// <value>
		/// The vertical scroll position of the page.
		/// </value>
		/// <remarks>
		/// Attempting to set this property outside the scroll bar's bounds forces the position to the nearest extreme.
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public int HorizontalScrollPosition
		{
			get
			{
				return horizontalScrollBar.Value;
			}
			set
			{
				SetScrollBarValue(horizontalScrollBar, value);

				// The above line doesn't always set scroll bar value, so make sure the page actually scrolled.
				SyncPageToHorizontalScrollBar();
			}
		}

		#region Designer generated code
		/// <summary> 
		///    Required method for Designer support - do not modify 
		///    the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
      this.horizontalScrollBar = new System.Windows.Forms.HScrollBar();
      this.verticalScrollBar = new System.Windows.Forms.VScrollBar();
      this.pageSite = new System.Windows.Forms.Panel();
      this.SuspendLayout();
      // 
      // horizontalScrollBar
      // 
      this.horizontalScrollBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.horizontalScrollBar.Enabled = false;
      this.horizontalScrollBar.Location = new System.Drawing.Point(0, 191);
      this.horizontalScrollBar.Name = "horizontalScrollBar";
      this.horizontalScrollBar.Size = new System.Drawing.Size(207, 17);
      this.horizontalScrollBar.TabIndex = 5;
      this.horizontalScrollBar.Visible = false;
      this.horizontalScrollBar.ValueChanged += new System.EventHandler(this.horizontalScrollBar_ValueChanged);
      // 
      // verticalScrollBar
      // 
      this.verticalScrollBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.verticalScrollBar.Enabled = false;
      this.verticalScrollBar.Font = new System.Drawing.Font("Tahoma", 8F);
      this.verticalScrollBar.ForeColor = System.Drawing.SystemColors.WindowText;
      this.verticalScrollBar.Location = new System.Drawing.Point(207, 0);
      this.verticalScrollBar.Name = "verticalScrollBar";
      this.verticalScrollBar.Size = new System.Drawing.Size(17, 191);
      this.verticalScrollBar.TabIndex = 4;
      this.verticalScrollBar.Visible = false;
      this.verticalScrollBar.ValueChanged += new System.EventHandler(this.verticalScrollBar_ValueChanged);
      // 
      // pageSite
      // 
      this.pageSite.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
        | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
      this.pageSite.BackColor = System.Drawing.SystemColors.Control;
      this.pageSite.Location = new System.Drawing.Point(0, 0);
      this.pageSite.Name = "pageSite";
      this.pageSite.Size = new System.Drawing.Size(208, 192);
      this.pageSite.TabIndex = 6;
      // 
      // Frame
      // 
      this.Controls.Add(this.pageSite);
      this.Controls.Add(this.horizontalScrollBar);
      this.Controls.Add(this.verticalScrollBar);
      this.Name = "Frame";
      this.Size = new System.Drawing.Size(224, 208);
      this.ResumeLayout(false);

    }
		#endregion

		/// <summary>
		/// Move n contexts backwards (if n is negative) or forwards (if n is positive) through the navigation history.
		/// </summary>
		/// <remarks>
		/// As we attempt to move through contexts, we ask the page in a given context
		/// if it wants to be shown in that context. If it doesn't want to be shown,
		/// we skip over it.
		/// 
		/// The context we end up is determined as follows. If the nth context is valid,
		/// we'll move to that one. If the nth context is not valid, we'll continue
		/// moving beyond it in the same direction until we find a valid context and
		/// move to that one. If we exhaust all contexts in that direction before
		/// finding a valid one, we move to the furthest valid context in that direction
		/// (the number of moves will be less than n). If there are no valid contexts
		/// at all in the desired direction, we stay where we are.
		/// </remarks>
		/// <param name="n">The number of contexts to move. If n is positive, move forward; if n is negative, move backwards.</param>
		private void MoveThroughHistory(int n)
		{
			if (!CanLeavePage)
			{
				// Current page wants us to stay where we are.
				return;
			}

			NavigationStack fromStack;		// Pop contexts off this stack.
			NavigationStack toStack;		// Push contexts onto this stack.
			int moveSteps;

			if (n < 0)
			{
				// Move backwards.
				fromStack = backStack;
				toStack = forwardStack;
				moveSteps = -n;
			}
			else if (n > 0)
			{
				// Move forwards.
				fromStack = forwardStack;
				toStack = backStack;
				moveSteps = n;
			}
			else
			{
				// Stay where we are.
				return;
			}

			/*
			 * Pop n contexts off the "from" stack. Look for valid contexts as we
			 * do this, i.e., contexts where the page wants to be shown. Push
			 * valid contexts that we pass over onto the "to" stack. Stop if we
			 * empty the "from" stack.
			 */
			NavigationContext lastValidContext = CurrentContext;
			bool foundDesiredContext = false;
			int poppedContextCount = 0;
			while (!foundDesiredContext && fromStack.Count > 0)
			{
				NavigationContext popContext = (NavigationContext) fromStack.Pop();
				poppedContextCount++;

				// Ask the page if it wants to be seen in this context.
				Page page = popContext.Page;
				if (page != null)
				{
					ViewState viewState = popContext.ViewState;
					bool canEnterPage = page.CanEnter(viewState);
					if (canEnterPage)
					{
						/*
						 * The page said it is okay to show the page in this context.
						 * Push the last valid context on the "to" stack. The context
						 * we just popped then becomes the last valid context we've seen.
						 */
						if (lastValidContext != null)
						{
							toStack.Push(lastValidContext);
						}
						lastValidContext = popContext;

						if (poppedContextCount >= moveSteps)
						{
							/*
							 * We've found a valid context after moving at least n steps
							 * through history, so we can stop on this one.
							 */
							foundDesiredContext = true;
						}
					}
					else
					{
						// The page no longer wants to be shown, so remove it.
						RemovePageFromFrame(page);
					}
				}
			}

			/*
			 * Switch to the last valid context we saw.
			 * If we never found a valid context, we'll stay where we are.
			 */
			if (lastValidContext != CurrentContext)
			{
				CurrentContext = lastValidContext;
			}
		}

		/// <summary>
		/// The current page has changed its ScrollBars property.
		/// </summary>
		private void Page_ScrollBarsChanged(object sender, System.EventArgs e)
		{
			SetupScrollBarsForPage(CurrentPage);
			ResizePage(CurrentPage);
		}

		/// <summary>
		/// Scroll the page down one screen's worth.
		/// </summary>
		/// <remarks>
		/// This has no effect if the page cannot be scrolled, or is already scrolled to the bottom.
		/// </remarks>
		public void PageDown()
		{
			VerticalScrollPosition += verticalScrollBar.LargeChange;
		}

		/// <summary>
		/// Scroll the page to the bottom.
		/// </summary>
		/// <remarks>
		/// This has no effect if the page cannot be scrolled, or is already scrolled to the bottom.
		/// </remarks>
		public void PageEnd()
		{
			VerticalScrollPosition = verticalScrollBar.Maximum - verticalScrollBar.LargeChange;
		}

		/// <summary>
		/// Scroll the page to the top.
		/// </summary>
		/// <remarks>
		/// This has no effect if the page cannot be scrolled, or is already scrolled to the top.
		/// </remarks>
		public void PageHome()
		{
			VerticalScrollPosition = 0;
		}

		/// <summary>
		/// Scroll the page up one screen's worth.
		/// </summary>
		/// <remarks>
		/// This has no effect if the page cannot be scrolled, or is already scrolled to the top.
		/// </remarks>
		public void PageUp()
		{
			VerticalScrollPosition -= verticalScrollBar.LargeChange;
		}

		/// <summary>
		/// Respond to a mouse wheel event by scrolling the current page.
		/// </summary>
		protected override void OnMouseWheel(MouseEventArgs e)
		{
			// One notch of a mouse wheel.
			const int WHEEL_DELTA_PER_NOTCH = 120;

			if (verticalScrollBar.Enabled)
			{
				// Determine how many notches the user has moved the wheel.
				int wheelNotches = e.Delta / WHEEL_DELTA_PER_NOTCH;

				// We'll scroll several lines of text (in the page's current font) for each notch of the wheel.
				int lineHeight = CurrentPage.Font.Height;
				int linesToScrollPerNotch = SystemInformation.MouseWheelScrollLines;
				int scrollBarDelta = linesToScrollPerNotch * lineHeight * wheelNotches;

				// Scroll the page vertically.
				VerticalScrollPosition -= scrollBarDelta;
			}

			base.OnMouseWheel(e);
		}

		/// <summary>
		/// Trigger the Navigate event.
		/// </summary>
		protected virtual void OnNavigate(NavigateEventArgs e)
		{
			if (Navigate != null)
			{
				Navigate(this, e);
			}
		}

		/// <summary>
		/// The frame is being resized; resize the current page.
		/// </summary>
		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);

			if (CurrentPage != null)
			{
				ResizePage(CurrentPage);
			}
		}

		/// <summary>
		/// Remove the specified page from the frame.
		/// </summary>
		/// <remarks>
		/// Since a single page instance may appear multiple times in the navigation history, we track
		/// the number of times a page appears in the history using reference counts. We only really remove
		/// the page from the frame if it no longer appears in the navigation history at all.
		/// </remarks>
		/// <param name="page">The page to remove</param>
		private void RemovePageFromFrame(Page page)
		{
			if (pageReferenceCount.RemoveReference(page) == 0)
			{
				// The page no longer appears in the navigation history at all, and can be safely removed.
				pageSite.Controls.Remove(page);
				page.Frame = null;
			}
		}

		/// <summary>
		/// Depending upon the page's scroll bar preferences, resize the page's
		/// height and width to match that of the frame's client area.
		/// </summary>
		private void ResizePage(Page page)
		{
			int newPageWidth = page.Width;
			int newPageHeight = page.Height;

			switch (page.ScrollBars)
			{
				case ScrollBars.Both:
					AdjustScrollBar(horizontalScrollBar, ClientSize.Width - verticalScrollBar.Width, page.Width);
					AdjustScrollBar(verticalScrollBar, ClientSize.Height - horizontalScrollBar.Height, page.Height);
					break;

				case ScrollBars.Horizontal:
					AdjustScrollBar(horizontalScrollBar, ClientSize.Width, page.Width);
					newPageHeight = ClientSize.Height;
					break;

				case ScrollBars.Vertical:
					AdjustScrollBar(verticalScrollBar, ClientSize.Height, page.Height);
					newPageWidth = ClientSize.Width;
					break;

				case ScrollBars.None:
					newPageHeight = ClientSize.Height;
					newPageWidth = ClientSize.Width;
					break;
			}

			// Only bother adjusting the page's size if it needs to be changed.
			Size newPageSize = new Size(newPageWidth, newPageHeight);
			if (page.Size != newPageSize)
			{
				page.Size = newPageSize;
			}
		}

		/// <summary>
		/// Set the value of the given scroll bar, forcing the value to be within the scroll bar's bounds.
		/// </summary>
		private void SetScrollBarValue(ScrollBar scrollBar, int scrollValue)
		{
			// Ensure scroll value is not too small or too big.
			scrollValue = Math.Max(scrollValue, 0);
			scrollValue = Math.Min(scrollValue, scrollBar.Maximum - scrollBar.LargeChange);

			scrollBar.Value = scrollValue;
		}

		/// <summary>
		/// Show or hide scroll bars according to the page's stated preference.
		/// </summary>
		/// <remarks>
		/// If the given page is null, this hides both scroll bars.
		/// </remarks>
		/// <param name="page">The page requesting scroll bars</param>
		private void SetupScrollBarsForPage(Page page)
		{
			ScrollBars scrollBars = (page == null) ? ScrollBars.None : page.ScrollBars;

			switch (scrollBars)
			{
				case ScrollBars.Both:
					horizontalScrollBar.Width = ClientSize.Width - verticalScrollBar.Width;
					verticalScrollBar.Height = ClientSize.Height - horizontalScrollBar.Height;
					horizontalScrollBar.Show();
					verticalScrollBar.Show();
					pageSite.Size = new Size(verticalScrollBar.Left, horizontalScrollBar.Top);
					break;

				case ScrollBars.Horizontal:
					horizontalScrollBar.Width = ClientSize.Width;
					horizontalScrollBar.Show();
					verticalScrollBar.Hide();
					pageSite.Size = new Size(ClientSize.Width, horizontalScrollBar.Top);
					page.Top = 0;
					break;

				case ScrollBars.None:
					horizontalScrollBar.Hide();
					verticalScrollBar.Hide();
					pageSite.Size = ClientSize;
					if (page != null)
					{
						page.Location = new Point(0, 0);
					}
					break;

				case ScrollBars.Vertical:
					verticalScrollBar.Height = ClientSize.Height;
					horizontalScrollBar.Hide();
					verticalScrollBar.Show();
					pageSite.Size = new Size(verticalScrollBar.Left, ClientSize.Height);
					page.Left = 0;
					break;
			}

			// Disable any scroll bar that just got hidden.
			if (!horizontalScrollBar.Visible)
			{
				horizontalScrollBar.Enabled = false;
			}
			if (!verticalScrollBar.Visible)
			{
				verticalScrollBar.Enabled = false;
			}
		}

		/// <summary>
		/// Make sure the page is scrolled to the position indicated by the horizontal scroll bar.
		/// </summary>
		private void SyncPageToHorizontalScrollBar()
		{
			int scrollOffset = -horizontalScrollBar.Value;

			if (CurrentPage.Left != scrollOffset)
			{
				CurrentPage.Left = scrollOffset;
			}
		}

		/// <summary>
		/// Make sure the page is scrolled to the position indicated by the vertical scroll bar.
		/// </summary>
		private void SyncPageToVerticalScrollBar()
		{
			int scrollOffset = -verticalScrollBar.Value;

			if (CurrentPage.Top != scrollOffset)
			{
				CurrentPage.Top = scrollOffset;
			}
		}

		/// <summary>
		/// The vertical scroll bar has been moved.
		/// </summary>
		private void verticalScrollBar_ValueChanged(object sender, System.EventArgs e)
		{
			SyncPageToVerticalScrollBar();
		}

		/// <value>
		/// The vertical scroll position of the page.
		/// </value>
		/// <remarks>
		/// Attempting to set this property outside the scroll bar's bounds forces the position to the nearest extreme.
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public int VerticalScrollPosition
		{
			get
			{
				return verticalScrollBar.Value;
			}
			set
			{
				SetScrollBarValue(verticalScrollBar, value);

				// The above line doesn't always set scroll bar value, so make sure the page actually scrolled.
				SyncPageToVerticalScrollBar();
			}
		}
	}
}
